﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using MacGruber;
using System;
using System.Linq;
using MeshVR;
using Request = MeshVR.AssetLoader.AssetBundleFromFileRequest;
using SimpleJSON;

public class RopeManager : MVRScript
{

    private UltimateRope rope;
    private Transform lastEndGo;

    private Transform startTrans;

    private List<string> ropeTypes = new List<string> { UltimateRope.ERopeType.Procedural, UltimateRope.ERopeType.LinkedObjects, UltimateRope.ERopeType.ImportBones };
    private List<string> colliderTypes = new List<string> { UltimateRope.EColliderType.None, UltimateRope.EColliderType.Capsule, UltimateRope.EColliderType.Box };
    private List<string> bundleAssetNames;
    private Request currentRequest;

    private JSONStorableBool isCreated;

    // segement parameter
    private JSONStorableFloat storableSegmentLength;
    private JSONStorableFloat storableSegmentNumLinks;
    private JSONStorableStringChooser storableSegmentColliderType;      // collider type
    private JSONStorableFloat storableSegmentLinkColliderSkipCount;     // collider skip

    // linkObject parameter
    private JSONStorableStringChooser storableLinkObjects;
    private JSONStorableStringChooser storableLinkAxis;
    private JSONStorableFloat storableLinkObjectOffset;
    private JSONStorableFloat storableLinkTwistStart;
    private JSONStorableFloat storableLinkTwistIncrement;

    // procedural parameter
    private JSONStorableFloat storableRopeDiameter;
    private JSONStorableFloat storableRopeSides;

    // common parameter
    private JSONStorableStringChooser storableRopeType;
    private JSONStorableBool storableIsExtensible;
    private JSONStorableFloat storableLinkMass;
    private JSONStorableFloat storableSolverIterations;
    private JSONStorableFloat storableJointDamper;
    private JSONStorableFloat storableJointSpring;
    private JSONStorableFloat storableJointMaxForce;
    private JSONStorableFloat storableJointBreakForce;
    private JSONStorableFloat storableJointBreakTorque;

    private JSONStorableParam storableRopeSaved;

    public override void Init()
    {
        base.Init();
        CommonUIInit();
        LoadBundleAssets();
    }

    private void LoadBundleAssets()
    {
        string assetsBundlePath = Utils.GetPluginPath(this) + "/rope.assetbundle";
        Request request = new Request { path = assetsBundlePath, callback = OnAssetBundleLoaded };
        AssetLoader.QueueLoadAssetBundleFromFile(request);
    }

    private void OnAssetBundleLoaded(Request request)
    {
        currentRequest = request;
        bundleAssetNames = currentRequest.assetBundle.GetAllAssetNames().ToList();

        RopeInit();
    }

    private void RopeInit()
    {
        LateUIInit();

        startTrans = containingAtom.reParentObject.Find("object/rescaleObject");
        lastEndGo = startTrans;
        containingAtom.reParentObject.Find("control").GetComponentInChildren<SphereCollider>().isTrigger = true;

        // start point
        rope = startTrans.gameObject.AddComponent<UltimateRope>();
        rope.RopeNodes = new List<UltimateRope.RopeNode>();
        rope.RopeStart = startTrans.gameObject;

        rope.LinkJointAngularXLimit = 180;
        rope.LinkJointAngularYLimit = 180;
        rope.LinkJointAngularZLimit = 180;
        rope.LinkJointDamperValue = storableJointDamper.val;

        rope.AutoRegenerate = true;

        // init setting
        rope.RopeType = UltimateRope.ERopeType.LinkedObjects;

        if (bundleAssetNames.Count > 0)
        {
            SetRopeLinkObject(bundleAssetNames[0]);
        }
        else
        {
            SuperController.LogError("There are no resources in the bundle package!");
            return;
        }

        // Restore saved results
        if (isCreated.val)
        {
            CreateTheRope();
        }
    }

    private void CommonUIInit()
    {
        // para
        storableRopeType = Utils.SetupStringChooser(this, "RopeType", ropeTypes, 1, false);
        storableRopeType.setCallbackFunction = (sel) => {
            if (rope != null)
            {
                rope.RopeType = sel;
                rope.Regenerate();
            }
        };

        Utils.SetupInfoText(this, "Physics Rope", 600, true);

        // ===========ropeType is LinkObjects
        storableLinkObjects = Utils.SetupStringChooser(this, "LinkObjects", new List<string>(), 0, true);
        storableLinkObjects.setCallbackFunction = (sel) =>
        {
            SetRopeLinkObject(sel);
        };

        storableLinkAxis = Utils.SetupStringChooser(this, "LinkObjects", new List<string> { UltimateRope.EAxis.X, UltimateRope.EAxis.Y, UltimateRope.EAxis.Z }, 0, true);
        storableLinkAxis.setCallbackFunction = (sel) => {
            if (rope != null)
            {
                rope.LinkAxis = sel;
            }
        };

        storableLinkObjectOffset = Utils.SetupSliderFloat(this, "Link Offset", 0f, -1f, 1f, true);
        storableLinkObjectOffset.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkOffsetObject = val;
            }
        };

        storableLinkTwistStart = Utils.SetupSliderFloat(this, "Link Twist Start", 0f, 0f, 360f, true);
        storableLinkTwistStart.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkTwistAngleStart = val;
            }
        };

        storableLinkTwistIncrement = Utils.SetupSliderFloat(this, "Link Twist Increment", 0f, 0f, 360f, true);
        storableLinkTwistIncrement.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkTwistAngleIncrement = val;
            }
        };

        // ===========Segement==============
        storableSegmentLength = Utils.SetupSliderFloat(this, "Segment Length", 1.5f, 0f, 5f, false);
        storableSegmentNumLinks = Utils.SetupSliderInt(this, "LinksNum", 20, 1, 100, false);
        storableSegmentColliderType = Utils.SetupStringChooser(this, "Collider Type", colliderTypes, 1, false);
        storableSegmentLinkColliderSkipCount = Utils.SetupSliderInt(this, "Collider Num To Skip", 1, 0, 10, false);

        // ========================================
        storableRopeDiameter = Utils.SetupSliderFloatWithRange(this, "Rope Diameter", 0.01f, 0.0001f, 0.25f, false);
        storableRopeDiameter.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.RopeDiameter = val;
            }
        };
        storableRopeSides = Utils.SetupSliderInt(this, "RopeSides", 8, 3, 16, false);
        storableRopeSides.setCallbackFunction = (val) =>
        {
            if (rope != null)
            {
                rope.RopeSegmentSides = (int)val;
            }
        };
        storableIsExtensible = Utils.SetupToggle(this, "IsExtensible", false, false);
        storableIsExtensible.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.IsExtensible = val;
            }
        };
        storableLinkMass = Utils.SetupSliderFloat(this, "LinksMass", 1f, -1f, 1f, false);
        storableLinkMass.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkMass = val;
            }
        };
        storableSolverIterations = Utils.SetupSliderInt(this, "SolverIterations", 70, 1, 255, false);
        storableSolverIterations.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkSolverIterationCount = (int)val;
            }
        };
        storableJointSpring = Utils.SetupSliderFloat(this, "Joint Spring", 1f, 0f, 1f, false);
        storableJointSpring.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkJointSpringValue = val;
            }
        };
        storableJointDamper = Utils.SetupSliderFloat(this, "Joint Damper", 0.8f, 0f, 1f, false);
        storableJointDamper.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkJointDamperValue = val;
            }
        };
        storableJointMaxForce = Utils.SetupSliderFloat(this, "Joint MaxForce", 1f, 0f, 1f, false);
        storableJointMaxForce.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkJointMaxForceValue = val;
            }
        };
        storableJointBreakForce = Utils.SetupSliderFloat(this, "Joint BreakForce", 10000f, 0f, 10000f, false);
        storableJointBreakForce.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkJointBreakForce = val;
            }
        };
        storableJointBreakTorque = Utils.SetupSliderFloat(this, "Joint BreakTorque", 10000f, 0f, 10000f, false);
        storableJointBreakTorque.setCallbackFunction = (val) => {
            if (rope != null)
            {
                rope.LinkJointBreakTorque = val;
            }
        };

        isCreated = Utils.SetupToggle(this, "IsCreated", false, false);

        Utils.SetupButton(this, "Generate", CreateTheRope, false);

        Utils.SetupButton(this, "ReGenerate", () => {
            rope.Regenerate();
        }, false);

        Utils.SetupButton(this, "Add Node", () => {
            if (rope != null)
            {
                CreateNewNode(rope.RopeNodes.Count - 1, (at) => {
                    rope.Regenerate();
                });
            }
        }, false);

        Utils.SetupButton(this, "Remove Unused Node", () => {
            RemoveUnusedNode();
        }, false);
    }

    private void LateUIInit()
    {
        storableLinkObjects.choices = bundleAssetNames;
    }

    private void SetRopeLinkObject(string sel)
    {
        if (rope != null)
        {
            try
            {
                var prefabObject = currentRequest.assetBundle.LoadAsset<GameObject>(sel);
                var meshRenders = prefabObject.GetComponentsInChildren<Renderer>();
                var targetShader = Shader.Find("Custom/Subsurface/TransparentGlossNMNoCullSeparateAlpha");
                meshRenders.ToList().ForEach(r => r.material.shader = targetShader);
                rope.LinkObject = prefabObject;
            }
            catch (Exception e)
            {
                SuperController.LogError(e.Message);
                throw;
            }
        }
    }

    private void CreateTheRope()
    {
        CreateNewNode(-1, (at) => {
            try
            {
                rope.Regenerate();

                rope.RopeMaterial = new Material(Shader.Find("Custom/Subsurface/TransparentGlossNMNoCullSeparateAlpha"));
                rope.RopeSectionMaterial = new Material(Shader.Find("Custom/Subsurface/TransparentGlossNMNoCullSeparateAlpha"));
                rope.SetupRopeMaterials();

                rope.RopeDiameter = storableRopeDiameter.val;
                rope.ChangeRopeDiameter(rope.RopeDiameter, rope.RopeDiameterScaleX, rope.RopeDiameterScaleY);

                isCreated.val = true;
            }
            catch (Exception e)
            {
                SuperController.LogMessage(e.Message);
                throw;
            }
        });
    }

    private void CreateNewNode(int index, Action<Atom> callback)
    {
        if (lastEndGo != null)
        {
            StartCoroutine(CreateAtomCo("Empty", NewUID("RopeNode"), (at) => {

                var endNodeTrans = at.containingAtom.reParentObject.Find("control");
                at.containingAtom.reParentObject.Find("control").GetComponentInChildren<Collider>().isTrigger = true;
                endNodeTrans.transform.position = lastEndGo.transform.position + Vector3.right * 1;
                lastEndGo = endNodeTrans;

                var node = rope.CreateNewNode(index);
                node.fLength = storableSegmentLength.val;
                node.nNumLinks = (int)storableSegmentNumLinks.val;
                node.nColliderSkip = (int)storableSegmentLinkColliderSkipCount.val;
                node.eColliderType = storableSegmentColliderType.val;
                node.goNode = endNodeTrans.gameObject;

                if (callback != null) callback(at);
            }));
        }
    }

    private void RemoveUnusedNode()
    {
        if (rope.RopeNodes.Count > 0)
        {
            for (int i = 0; i < rope.RopeNodes.Count; i++)
            {
                if (rope.RopeNodes[i].goNode == null)
                {
                    rope.RopeNodes.RemoveAt(i);
                }
            }
        }
    }

    private void OnDestroy()
    {
    }

    //from Acidbubbles on Discord 27/02/2021
    public static IEnumerator CreateAtomCo(string type, string uid, Action<Atom> callback)
    {
        IEnumerator enumerator = SuperController.singleton.AddAtomByType(type, uid, true);
        while (enumerator.MoveNext())
        {
            yield return enumerator.Current;
        }
        Atom atom = SuperController.singleton.GetAtomByUid(uid);
        if (atom == null)
        {
            throw new NullReferenceException("Atom did not spawn");
        }
        callback(atom);
    }

    //adapted from Acidbubbles on Discord 27/02/2021
    public static string NewUID(string basename)
    {
        HashSet<string> uids = new HashSet<string>(SuperController.singleton.GetAtomUIDs());
        if (!uids.Contains(basename))
        {
            return basename;
        }

        for (int i = 2; i < 1000; i++)
        {
            string uid = $"{basename}#{i}";
            if (!uids.Contains(uid))
            {
                return uid;
            }
        }

        return basename + Guid.NewGuid();
    }
}
